1   /*
2    * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package com.sun.media.sound;
26  
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  
32  import javax.sound.sampled.AudioFormat;
33  import javax.sound.sampled.AudioInputStream;
34  import javax.sound.sampled.AudioSystem;
35  import javax.sound.sampled.AudioFormat.Encoding;
36  import javax.sound.sampled.spi.FormatConversionProvider;
37  
38  /**
39   * This class is used to convert between 8,16,24,32 bit signed/unsigned
40   * big/litle endian fixed/floating stereo/mono/multi-channel audio streams and
41   * perform sample-rate conversion if needed.
42   *
43   * @author Karl Helgason
44   */
45  public class AudioFloatFormatConverter extends FormatConversionProvider {
46  
47      private static class AudioFloatFormatConverterInputStream extends
48              InputStream {
49          private AudioFloatConverter converter;
50  
51          private AudioFloatInputStream stream;
52  
53          private float[] readfloatbuffer;
54  
55          private int fsize = 0;
56  
57          public AudioFloatFormatConverterInputStream(AudioFormat targetFormat,
58                  AudioFloatInputStream stream) {
59              this.stream = stream;
60              converter = AudioFloatConverter.getConverter(targetFormat);
61              fsize = ((targetFormat.getSampleSizeInBits() + 7) / 8);
62          }
63  
64          public int read() throws IOException {
65              byte[] b = new byte[1];
66              int ret = read(b);
67              if (ret < 0)
68                  return ret;
69              return b[0] & 0xFF;
70          }
71  
72          public int read(byte[] b, int off, int len) throws IOException {
73  
74              int flen = len / fsize;
75              if (readfloatbuffer == null || readfloatbuffer.length < flen)
76                  readfloatbuffer = new float[flen];
77              int ret = stream.read(readfloatbuffer, 0, flen);
78              if (ret < 0)
79                  return ret;
80              converter.toByteArray(readfloatbuffer, 0, ret, b, off);
81              return ret * fsize;
82          }
83  
84          public int available() throws IOException {
85              int ret = stream.available();
86              if (ret < 0)
87                  return ret;
88              return ret * fsize;
89          }
90  
91          public void close() throws IOException {
92              stream.close();
93          }
94  
95          public synchronized void mark(int readlimit) {
96              stream.mark(readlimit * fsize);
97          }
98  
99          public boolean markSupported() {
100             return stream.markSupported();
101         }
102 
103         public synchronized void reset() throws IOException {
104             stream.reset();
105         }
106 
107         public long skip(long n) throws IOException {
108             long ret = stream.skip(n / fsize);
109             if (ret < 0)
110                 return ret;
111             return ret * fsize;
112         }
113 
114     }
115 
116     private static class AudioFloatInputStreamChannelMixer extends
117             AudioFloatInputStream {
118 
119         private int targetChannels;
120 
121         private int sourceChannels;
122 
123         private AudioFloatInputStream ais;
124 
125         private AudioFormat targetFormat;
126 
127         private float[] conversion_buffer;
128 
129         public AudioFloatInputStreamChannelMixer(AudioFloatInputStream ais,
130                 int targetChannels) {
131             this.sourceChannels = ais.getFormat().getChannels();
132             this.targetChannels = targetChannels;
133             this.ais = ais;
134             AudioFormat format = ais.getFormat();
135             targetFormat = new AudioFormat(format.getEncoding(), format
136                     .getSampleRate(), format.getSampleSizeInBits(),
137                     targetChannels, (format.getFrameSize() / sourceChannels)
138                             * targetChannels, format.getFrameRate(), format
139                             .isBigEndian());
140         }
141 
142         public int available() throws IOException {
143             return (ais.available() / sourceChannels) * targetChannels;
144         }
145 
146         public void close() throws IOException {
147             ais.close();
148         }
149 
150         public AudioFormat getFormat() {
151             return targetFormat;
152         }
153 
154         public long getFrameLength() {
155             return ais.getFrameLength();
156         }
157 
158         public void mark(int readlimit) {
159             ais.mark((readlimit / targetChannels) * sourceChannels);
160         }
161 
162         public boolean markSupported() {
163             return ais.markSupported();
164         }
165 
166         public int read(float[] b, int off, int len) throws IOException {
167             int len2 = (len / targetChannels) * sourceChannels;
168             if (conversion_buffer == null || conversion_buffer.length < len2)
169                 conversion_buffer = new float[len2];
170             int ret = ais.read(conversion_buffer, 0, len2);
171             if (ret < 0)
172                 return ret;
173             if (sourceChannels == 1) {
174                 int cs = targetChannels;
175                 for (int c = 0; c < targetChannels; c++) {
176                     for (int i = 0, ix = off + c; i < len2; i++, ix += cs) {
177                         b[ix] = conversion_buffer[i];
178                     }
179                 }
180             } else if (targetChannels == 1) {
181                 int cs = sourceChannels;
182                 for (int i = 0, ix = off; i < len2; i += cs, ix++) {
183                     b[ix] = conversion_buffer[i];
184                 }
185                 for (int c = 1; c < sourceChannels; c++) {
186                     for (int i = c, ix = off; i < len2; i += cs, ix++) {
187                         b[ix] += conversion_buffer[i];
188                     }
189                 }
190                 float vol = 1f / ((float) sourceChannels);
191                 for (int i = 0, ix = off; i < len2; i += cs, ix++) {
192                     b[ix] *= vol;
193                 }
194             } else {
195                 int minChannels = Math.min(sourceChannels, targetChannels);
196                 int off_len = off + len;
197                 int ct = targetChannels;
198                 int cs = sourceChannels;
199                 for (int c = 0; c < minChannels; c++) {
200                     for (int i = off + c, ix = c; i < off_len; i += ct, ix += cs) {
201                         b[i] = conversion_buffer[ix];
202                     }
203                 }
204                 for (int c = minChannels; c < targetChannels; c++) {
205                     for (int i = off + c; i < off_len; i += ct) {
206                         b[i] = 0;
207                     }
208                 }
209             }
210             return (ret / sourceChannels) * targetChannels;
211         }
212 
213         public void reset() throws IOException {
214             ais.reset();
215         }
216 
217         public long skip(long len) throws IOException {
218             long ret = ais.skip((len / targetChannels) * sourceChannels);
219             if (ret < 0)
220                 return ret;
221             return (ret / sourceChannels) * targetChannels;
222         }
223 
224     }
225 
226     private static class AudioFloatInputStreamResampler extends
227             AudioFloatInputStream {
228 
229         private AudioFloatInputStream ais;
230 
231         private AudioFormat targetFormat;
232 
233         private float[] skipbuffer;
234 
235         private SoftAbstractResampler resampler;
236 
237         private float[] pitch = new float[1];
238 
239         private float[] ibuffer2;
240 
241         private float[][] ibuffer;
242 
243         private float ibuffer_index = 0;
244 
245         private int ibuffer_len = 0;
246 
247         private int nrofchannels = 0;
248 
249         private float[][] cbuffer;
250 
251         private int buffer_len = 512;
252 
253         private int pad;
254 
255         private int pad2;
256 
257         private float[] ix = new float[1];
258 
259         private int[] ox = new int[1];
260 
261         private float[][] mark_ibuffer = null;
262 
263         private float mark_ibuffer_index = 0;
264 
265         private int mark_ibuffer_len = 0;
266 
267         public AudioFloatInputStreamResampler(AudioFloatInputStream ais,
268                 AudioFormat format) {
269             this.ais = ais;
270             AudioFormat sourceFormat = ais.getFormat();
271             targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
272                     .getSampleRate(), sourceFormat.getSampleSizeInBits(),
273                     sourceFormat.getChannels(), sourceFormat.getFrameSize(),
274                     format.getSampleRate(), sourceFormat.isBigEndian());
275             nrofchannels = targetFormat.getChannels();
276             Object interpolation = format.getProperty("interpolation");
277             if (interpolation != null && (interpolation instanceof String)) {
278                 String resamplerType = (String) interpolation;
279                 if (resamplerType.equalsIgnoreCase("point"))
280                     this.resampler = new SoftPointResampler();
281                 if (resamplerType.equalsIgnoreCase("linear"))
282                     this.resampler = new SoftLinearResampler2();
283                 if (resamplerType.equalsIgnoreCase("linear1"))
284                     this.resampler = new SoftLinearResampler();
285                 if (resamplerType.equalsIgnoreCase("linear2"))
286                     this.resampler = new SoftLinearResampler2();
287                 if (resamplerType.equalsIgnoreCase("cubic"))
288                     this.resampler = new SoftCubicResampler();
289                 if (resamplerType.equalsIgnoreCase("lanczos"))
290                     this.resampler = new SoftLanczosResampler();
291                 if (resamplerType.equalsIgnoreCase("sinc"))
292                     this.resampler = new SoftSincResampler();
293             }
294             if (resampler == null)
295                 resampler = new SoftLinearResampler2(); // new
296                                                         // SoftLinearResampler2();
297             pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
298             pad = resampler.getPadding();
299             pad2 = pad * 2;
300             ibuffer = new float[nrofchannels][buffer_len + pad2];
301             ibuffer2 = new float[nrofchannels * buffer_len];
302             ibuffer_index = buffer_len + pad;
303             ibuffer_len = buffer_len;
304         }
305 
306         public int available() throws IOException {
307             return 0;
308         }
309 
310         public void close() throws IOException {
311             ais.close();
312         }
313 
314         public AudioFormat getFormat() {
315             return targetFormat;
316         }
317 
318         public long getFrameLength() {
319             return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
320         }
321 
322         public void mark(int readlimit) {
323             ais.mark((int) (readlimit * pitch[0]));
324             mark_ibuffer_index = ibuffer_index;
325             mark_ibuffer_len = ibuffer_len;
326             if (mark_ibuffer == null) {
327                 mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
328             }
329             for (int c = 0; c < ibuffer.length; c++) {
330                 float[] from = ibuffer[c];
331                 float[] to = mark_ibuffer[c];
332                 for (int i = 0; i < to.length; i++) {
333                     to[i] = from[i];
334                 }
335             }
336         }
337 
338         public boolean markSupported() {
339             return ais.markSupported();
340         }
341 
342         private void readNextBuffer() throws IOException {
343 
344             if (ibuffer_len == -1)
345                 return;
346 
347             for (int c = 0; c < nrofchannels; c++) {
348                 float[] buff = ibuffer[c];
349                 int buffer_len_pad = ibuffer_len + pad2;
350                 for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
351                     buff[ix] = buff[i];
352                 }
353             }
354 
355             ibuffer_index -= (ibuffer_len);
356 
357             ibuffer_len = ais.read(ibuffer2);
358             if (ibuffer_len >= 0) {
359                 while (ibuffer_len < ibuffer2.length) {
360                     int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
361                             - ibuffer_len);
362                     if (ret == -1)
363                         break;
364                     ibuffer_len += ret;
365                 }
366                 Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
367                 ibuffer_len /= nrofchannels;
368             } else {
369                 Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
370             }
371 
372             int ibuffer2_len = ibuffer2.length;
373             for (int c = 0; c < nrofchannels; c++) {
374                 float[] buff = ibuffer[c];
375                 for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
376                     buff[ix] = ibuffer2[i];
377                 }
378             }
379 
380         }
381 
382         public int read(float[] b, int off, int len) throws IOException {
383 
384             if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
385                 cbuffer = new float[nrofchannels][len / nrofchannels];
386             }
387             if (ibuffer_len == -1)
388                 return -1;
389             if (len < 0)
390                 return 0;
391             int offlen = off + len;
392             int remain = len / nrofchannels;
393             int destPos = 0;
394             int in_end = ibuffer_len;
395             while (remain > 0) {
396                 if (ibuffer_len >= 0) {
397                     if (ibuffer_index >= (ibuffer_len + pad))
398                         readNextBuffer();
399                     in_end = ibuffer_len + pad;
400                 }
401 
402                 if (ibuffer_len < 0) {
403                     in_end = pad2;
404                     if (ibuffer_index >= in_end)
405                         break;
406                 }
407 
408                 if (ibuffer_index < 0)
409                     break;
410                 int preDestPos = destPos;
411                 for (int c = 0; c < nrofchannels; c++) {
412                     ix[0] = ibuffer_index;
413                     ox[0] = destPos;
414                     float[] buff = ibuffer[c];
415                     resampler.interpolate(buff, ix, in_end, pitch, 0,
416                             cbuffer[c], ox, len / nrofchannels);
417                 }
418                 ibuffer_index = ix[0];
419                 destPos = ox[0];
420                 remain -= destPos - preDestPos;
421             }
422             for (int c = 0; c < nrofchannels; c++) {
423                 int ix = 0;
424                 float[] buff = cbuffer[c];
425                 for (int i = c + off; i < offlen; i += nrofchannels) {
426                     b[i] = buff[ix++];
427                 }
428             }
429             return len - remain * nrofchannels;
430         }
431 
432         public void reset() throws IOException {
433             ais.reset();
434             if (mark_ibuffer == null)
435                 return;
436             ibuffer_index = mark_ibuffer_index;
437             ibuffer_len = mark_ibuffer_len;
438             for (int c = 0; c < ibuffer.length; c++) {
439                 float[] from = mark_ibuffer[c];
440                 float[] to = ibuffer[c];
441                 for (int i = 0; i < to.length; i++) {
442                     to[i] = from[i];
443                 }
444             }
445 
446         }
447 
448         public long skip(long len) throws IOException {
449             if (len < 0)
450                 return 0;
451             if (skipbuffer == null)
452                 skipbuffer = new float[1024 * targetFormat.getFrameSize()];
453             float[] l_skipbuffer = skipbuffer;
454             long remain = len;
455             while (remain > 0) {
456                 int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
457                         skipbuffer.length));
458                 if (ret < 0) {
459                     if (remain == len)
460                         return ret;
461                     break;
462                 }
463                 remain -= ret;
464             }
465             return len - remain;
466 
467         }
468 
469     }
470 
471     private Encoding[] formats = { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
472             Encoding.PCM_FLOAT };
473 
474     public AudioInputStream getAudioInputStream(Encoding targetEncoding,
475             AudioInputStream sourceStream) {
476         if (sourceStream.getFormat().getEncoding().equals(targetEncoding))
477             return sourceStream;
478         AudioFormat format = sourceStream.getFormat();
479         int channels = format.getChannels();
480         Encoding encoding = targetEncoding;
481         float samplerate = format.getSampleRate();
482         int bits = format.getSampleSizeInBits();
483         boolean bigendian = format.isBigEndian();
484         if (targetEncoding.equals(Encoding.PCM_FLOAT))
485             bits = 32;
486         AudioFormat targetFormat = new AudioFormat(encoding, samplerate, bits,
487                 channels, channels * bits / 8, samplerate, bigendian);
488         return getAudioInputStream(targetFormat, sourceStream);
489     }
490 
491     public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
492             AudioInputStream sourceStream) {
493         if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
494             throw new IllegalArgumentException("Unsupported conversion: "
495                     + sourceStream.getFormat().toString() + " to "
496                     + targetFormat.toString());
497         return getAudioInputStream(targetFormat, AudioFloatInputStream
498                 .getInputStream(sourceStream));
499     }
500 
501     public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
502             AudioFloatInputStream sourceStream) {
503 
504         if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
505             throw new IllegalArgumentException("Unsupported conversion: "
506                     + sourceStream.getFormat().toString() + " to "
507                     + targetFormat.toString());
508         if (targetFormat.getChannels() != sourceStream.getFormat()
509                 .getChannels())
510             sourceStream = new AudioFloatInputStreamChannelMixer(sourceStream,
511                     targetFormat.getChannels());
512         if (Math.abs(targetFormat.getSampleRate()
513                 - sourceStream.getFormat().getSampleRate()) > 0.000001)
514             sourceStream = new AudioFloatInputStreamResampler(sourceStream,
515                     targetFormat);
516         return new AudioInputStream(new AudioFloatFormatConverterInputStream(
517                 targetFormat, sourceStream), targetFormat, sourceStream
518                 .getFrameLength());
519     }
520 
521     public Encoding[] getSourceEncodings() {
522         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
523                 Encoding.PCM_FLOAT };
524     }
525 
526     public Encoding[] getTargetEncodings() {
527         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
528                 Encoding.PCM_FLOAT };
529     }
530 
531     public Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
532         if (AudioFloatConverter.getConverter(sourceFormat) == null)
533             return new Encoding[0];
534         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
535                 Encoding.PCM_FLOAT };
536     }
537 
538     public AudioFormat[] getTargetFormats(Encoding targetEncoding,
539             AudioFormat sourceFormat) {
540         if (AudioFloatConverter.getConverter(sourceFormat) == null)
541             return new AudioFormat[0];
542         int channels = sourceFormat.getChannels();
543 
544         ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>();
545 
546         if (targetEncoding.equals(Encoding.PCM_SIGNED))
547             formats.add(new AudioFormat(Encoding.PCM_SIGNED,
548                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
549                     AudioSystem.NOT_SPECIFIED, false));
550         if (targetEncoding.equals(Encoding.PCM_UNSIGNED))
551             formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
552                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
553                     AudioSystem.NOT_SPECIFIED, false));
554 
555         for (int bits = 16; bits < 32; bits += 8) {
556             if (targetEncoding.equals(Encoding.PCM_SIGNED)) {
557                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
558                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
559                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
560                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
561                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
562                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
563             }
564             if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) {
565                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
566                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
567                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
568                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
569                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
570                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
571             }
572         }
573 
574         if (targetEncoding.equals(Encoding.PCM_FLOAT)) {
575             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
576                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
577                     AudioSystem.NOT_SPECIFIED, false));
578             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
579                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
580                     AudioSystem.NOT_SPECIFIED, true));
581             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
582                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
583                     AudioSystem.NOT_SPECIFIED, false));
584             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
585                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
586                     AudioSystem.NOT_SPECIFIED, true));
587         }
588 
589         return formats.toArray(new AudioFormat[formats.size()]);
590     }
591 
592     public boolean isConversionSupported(AudioFormat targetFormat,
593             AudioFormat sourceFormat) {
594         if (AudioFloatConverter.getConverter(sourceFormat) == null)
595             return false;
596         if (AudioFloatConverter.getConverter(targetFormat) == null)
597             return false;
598         if (sourceFormat.getChannels() <= 0)
599             return false;
600         if (targetFormat.getChannels() <= 0)
601             return false;
602         return true;
603     }
604 
605     public boolean isConversionSupported(Encoding targetEncoding,
606             AudioFormat sourceFormat) {
607         if (AudioFloatConverter.getConverter(sourceFormat) == null)
608             return false;
609         for (int i = 0; i < formats.length; i++) {
610             if (targetEncoding.equals(formats[i]))
611                 return true;
612         }
613         return false;
614     }
615 
616 }